/**
 * File:   modbus_prop.c
 * Author: AWTK Develop Team
 * Brief:  modbus_prop
 *
 * Copyright (c) 2023 - 2024  Guangzhou ZHIYUAN Electronics Co.,Ltd.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * License file for more details.
 *
 */

/**
 * History:
 * ================================================================
 * 2024-3-11 Li XianJing <xianjimli@hotmail.com> created
 *
 */

#include "modbus_prop.h"

modbus_prop_t* modbus_prop_create(tk_object_t* variables, const char* name) {
  modbus_prop_t* prop = TKMEM_ZALLOC(modbus_prop_t);
  return_value_if_fail(prop != NULL, NULL);

  if (modbus_prop_parse(prop, variables, name) != RET_OK) {
    modbus_prop_destroy(prop);
    prop = NULL;
  }

  return prop;
}

ret_t modbus_prop_parse(modbus_prop_t* prop, tk_object_t* variables, const char* name) {
  const char* p = NULL;
  return_value_if_fail(name != NULL && prop != NULL, RET_BAD_PARAMS);

  /*变量别名*/
  memset(prop, 0, sizeof(*prop));

  if (variables != NULL) {
    modbus_prop_t* var_prop = (modbus_prop_t*)tk_object_get_prop_pointer(variables, name);
    if (var_prop != NULL) {
      *prop = *var_prop;
      return RET_OK;
    }
  }

  p = strchr(name, '.');
  return_value_if_fail(p != NULL, RET_NOT_FOUND);

  tk_strncpy_s(prop->channel_name, sizeof(prop->channel_name) - 1, name, p - name);

  p++;
  prop->type = tk_basic_type_from_name(p);
  prop->channel_type = prop->type;
  return_value_if_fail(prop->type != VALUE_TYPE_INVALID, RET_NOT_FOUND);

  p = strchr(p, '[');
  return_value_if_fail(p != NULL, RET_NOT_FOUND);
  p++;
  prop->index = tk_atoi(p);
  return_value_if_fail(prop->index >= 0, RET_BAD_PARAMS);
  prop->offset = prop->index * tk_size_of_basic_type(prop->type);

  p = strchr(p, '.');
  prop->bit_offset = -1;
  if (p != NULL) {
    p++;
    if (tk_isdigit(*p)) {
      prop->bit_offset = tk_atoi(p);
      return_value_if_fail(prop->bit_offset >= 0, RET_BAD_PARAMS);
    } else {
      prop->type = tk_basic_type_from_name(p);
      if (prop->type == VALUE_TYPE_STRING || prop->type == VALUE_TYPE_BINARY) {
        const char* limit = strchr(p, '[');
        if (limit != NULL) {
          prop->limit = tk_atoi(limit + 1);
        }
      }
    }
  }

  prop->type_size = tk_size_of_basic_type(prop->type);

  return prop->type_size > 0 ? RET_OK : RET_NOT_FOUND;
}

static uint8_t* modbus_prop_skip_to(modbus_prop_t* prop) {
  return_value_if_fail(prop != NULL, NULL);
  return_value_if_fail(prop->type_size > 0, NULL);
  return_value_if_fail(prop->buffer != NULL, NULL);
  return_value_if_fail((prop->offset + prop->type_size) <= prop->buffer_length, NULL);

  return prop->buffer + prop->offset;
}

ret_t modbus_prop_get_value(modbus_prop_t* prop, value_t* v) {
  uint32_t len = 0;
  ret_t ret = RET_OK;
  uint8_t* data = modbus_prop_skip_to(prop);
  return_value_if_fail(data != NULL, RET_NOT_FOUND);
  len = prop->buffer_length - (data - prop->buffer);

  ret = tk_buffer_get_value(data, len, prop->type, 0, prop->bit_offset, v);
  if (ret == RET_OK) {
    if (prop->type == VALUE_TYPE_BINARY) {
      binary_data_t* b = value_binary_data(v);
      if (prop->limit > 0) {
        b->size = tk_min_int(b->size, prop->limit);
      }
      v->sub_type = prop->channel_type;
    }
  }

  return ret;
}

ret_t modbus_prop_set_value(modbus_prop_t* prop, const value_t* v) {
  uint32_t len = 0;
  uint8_t* data = modbus_prop_skip_to(prop);
  return_value_if_fail(data != NULL, RET_NOT_FOUND);
  len = prop->buffer_length - (data - prop->buffer);

  if (prop->limit > 0) {
    if (prop->type == VALUE_TYPE_BINARY) {
      binary_data_t* b = value_binary_data(v);
      return_value_if_fail(b->size <= prop->limit, RET_BAD_PARAMS);
    }

    if (prop->type == VALUE_TYPE_STRING) {
      uint32_t len = tk_strlen(value_str(v));
      return_value_if_fail(len < prop->limit, RET_BAD_PARAMS);
    }
  }

  return tk_buffer_set_value(data, len, prop->type, 0, prop->bit_offset, v);
}

ret_t modbus_prop_destroy(modbus_prop_t* prop) {
  return_value_if_fail(prop != NULL, RET_BAD_PARAMS);

  memset(prop, 0, sizeof(*prop));
  TKMEM_FREE(prop);

  return RET_OK;
}